package com.raos.example.service.impl;

import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.raos.example.dao.EmployeeDao;
import com.raos.example.model.EmployeeEntity;
import com.raos.example.service.ExportService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author raos
 * @email 991207823@qq.com
 * @date 2023-08-26 9:30
 */
@Slf4j
@Service
public class ExportServiceImpl implements ExportService {

    public static final String CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";

    @Autowired
    private EmployeeDao employeeDao;

    @Override
    public void exportExcel1(HttpServletResponse response) throws IOException {
        setExportHeader(response, "one_sheet.xlsx");
        // 获取所有数据
        List<EmployeeEntity> doList = employeeDao.selectAll();
        // EasyExcel写入并导出
        EasyExcelFactory.write(response.getOutputStream(), EmployeeEntity.class)
                .sheet()
                .doWrite(doList);
    }

    @Override
    public void exportExcel2(HttpServletResponse response) throws IOException {
        setExportHeader(response, "multiple_sheet.xlsx");
        // 获取所有数据
        List<EmployeeEntity> doList = employeeDao.selectAll();
        try (ExcelWriter excelWriter = EasyExcelFactory.write(response.getOutputStream(), EmployeeEntity.class).build()) {
            // 定义3个sheet页
            WriteSheet writeSheet1 = EasyExcelFactory.writerSheet(1, "模板1").build();
            WriteSheet writeSheet2 = EasyExcelFactory.writerSheet(2, "模板2").build();
            WriteSheet writeSheet3 = EasyExcelFactory.writerSheet(3, "模板3").build();
            // 将数据分成3份
            List<EmployeeEntity> data1 = doList.subList(0, doList.size() / 3);
            List<EmployeeEntity> data2 = doList.subList(doList.size() / 3, doList.size() * 2 / 3);
            List<EmployeeEntity> data3 = doList.subList(doList.size() * 2 / 3, doList.size());
            // 写入对应的sheet页
            excelWriter.write(data1, writeSheet1);
            excelWriter.write(data2, writeSheet2);
            excelWriter.write(data3, writeSheet3);
        } catch (IOException e) {
            log.error("多个sheet导出出错", e);
        }
    }

    @Override
    public void exportExcel3(HttpServletResponse response) throws IOException {
        setExportHeader(response, "paged_query.xlsx");
        try (ExcelWriter excelWriter = EasyExcelFactory.write(response.getOutputStream(), EmployeeEntity.class).build()) {
            // 获取总的数量
            long count = employeeDao.selectCountByQuery(new QueryWrapper());
            int pages = 20;
            long size = count / pages;

            for (int i = 0; i < pages; i++) {
                WriteSheet writeSheet = EasyExcelFactory.writerSheet(i, "模板" + i).build();

                Page<EmployeeEntity> page = new Page<>();
                page.setPageNumber(i + 1L);
                page.setPageSize(size);
                Page<EmployeeEntity> selectPage = employeeDao.paginateAs(page, new QueryWrapper(), EmployeeEntity.class);
                excelWriter.write(selectPage.getRecords(), writeSheet);
            }
        } catch (IOException e) {
            log.error("分页查询（每页一个sheet）导出出错", e);
        }
    }

    @Override
    public void exportExcel4(HttpServletResponse response) throws IOException {
        setExportHeader(response, "multithreading_query.xlsx");
        //查询数据库的总条数
        long count = employeeDao.selectCountByQuery(new QueryWrapper());
        //多少个sheet
        int pages = 20;
        // 每个sheet的条数
        long size = count / pages;
        // 创建固定的线程数（多少个sheet就创建多少个线程）
        ExecutorService executorService = Executors.newFixedThreadPool(pages);
        CountDownLatch countDownLatch = new CountDownLatch(pages);
        // 注意：此处为什么用map接收每页数据，而不是直接写入excel中？ 答案是 EasyExcel 不支持多个线程写入sheet -- 官方解释：https://github.com/alibaba/easyexcel/issues/1040
        Map<Integer, Page<EmployeeEntity>> pageMap = new HashMap<>();
        try {
            for (int i = 0; i < pages; i++) {
                int finalI = i;
                executorService.submit(() -> {
                    Page<EmployeeEntity> page = new Page<>();
                    page.setPageNumber(finalI + 1L);
                    page.setPageSize(size);
                    Page<EmployeeEntity> selectPage = employeeDao.paginateAs(page, new QueryWrapper(), EmployeeEntity.class);
                    pageMap.put(finalI, selectPage);
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
        } catch (InterruptedException e) {
            log.error("", e);
            Thread.currentThread().interrupt();
        }

        try (ExcelWriter excelWriter = EasyExcelFactory.write(response.getOutputStream(), EmployeeEntity.class).build()) {
            for (Map.Entry<Integer, Page<EmployeeEntity>> entry : pageMap.entrySet()) {
                Integer num = entry.getKey();
                Page<EmployeeEntity> salariesPage = entry.getValue();
                WriteSheet writeSheet = EasyExcelFactory.writerSheet(num, "模板" + num).build();
                excelWriter.write(salariesPage.getRecords(), writeSheet);
            }
        } catch (IOException e) {
            log.error("分页查询（每页一个sheet）导出出错", e);
        }
    }

    private static void setExportHeader(HttpServletResponse response, String fileName) {
        if (!StringUtils.hasLength(fileName)) {
            fileName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")) + ".xlsx";
        }
        response.setContentType(CONTENT_TYPE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
    }

}
